//
//  MainStatesPanelVC.swift
//  Hermes
//
//  Created by FH on 2021/5/19.
//  Copyright © 2021 Agora. All rights reserved.
//

import Foundation

let lblMinWidth: CGFloat = 70
let btnMinWidth: CGFloat = 110
let inputWidth: CGFloat = 134
let selectWidth: CGFloat = 160
let lblHeight: CGFloat = 20
let inputHeight: CGFloat = 24
let selectHeight: CGFloat = 30
let btnHeight: CGFloat = 26
let lblMarginLeft: CGFloat = 4
let selectMarginLeft: CGFloat = 0
let inputMarginLeft: CGFloat = 2
let btnMarginLeft: CGFloat = 4

let controlWidths: [StateMappingControlType : CGFloat] = [
    .checkbox : lblMinWidth,
    .dropbox : selectWidth,
    .inputField : inputWidth,
    .inputFile : inputWidth,
    .inputBinary : inputWidth
]

let controlMarginLefts: [StateMappingControlType : CGFloat] = [
    .checkbox : btnMarginLeft,
    .dropbox : selectMarginLeft,
    .inputField : inputMarginLeft,
    .inputFile : inputMarginLeft,
    .inputBinary : inputMarginLeft
]
/////////////////////////////////////////////

class MainStatesPanelVC : FLViewController {
    private let appContext = HermesContext()
    private let appContextSpan = NSView()
    private let customSpan = NSView()
    // controls mapping appContext's States
    private let inputChannelId = FLInputField()
    private let selectChannelProfile = NSPopUpButton()
    private let selectClientRole = NSPopUpButton()
    private let selectAppId = NSPopUpButton()
    private let inputAppId = FLInputField()
    private let inputUserId = FLInputField()
    private let selectTokenType = NSPopUpButton()
    private let inputToken = FLInputField()
    private let btnStopCapture = NSButton()
    private let btnJoinChannel = NSButton()
    private let btnBeforeParams = NSButton()
    private let btnAfterParams = NSButton()
    private let btnSettings = NSButton()
    private let imgSettingsTips = NSImageView()
    // states, TODO: save to preferences
    private var manualSelectChannelProfile = false
    private var manualSelectTokenType = false
    private var manualSelectAppId = false
    private var manualInputAppId = false
    private var manualInputUserId: UInt? = nil
    
    // REMARK: data binding
    
    private func resetManualStates() {
        self.manualSelectChannelProfile = false
        self.manualSelectTokenType = false
        self.manualSelectAppId = false
        self.manualInputAppId = false
        print(">>> todo: remove, handle preference")
    }
    
    private func dataBinding(_ needsLayout: Bool) {
        if self.selectChannelProfile.itemTitles.count == 0 {
            self.selectChannelProfile.addItems(withTitles: getCaseItemDescriptions(by: "AgoraChannelProfile", appContext.engine))
            self.selectClientRole.addItems(withTitles: getCaseItemDescriptions(by: "AgoraClientRole",
                                                                               defaultItem: " ",
                                                                               appContext.engine))
            self.selectTokenType.addItems(withTitles: AppTokenType.allCaseDescriptions)
            self.selectAppId.addItems(withTitles: AppAppIdType.allCaseDescriptions)
            // observer switch script
            self.appContext.$loadScriptTrigger.signal()
                // skip(1) is trigger by default load of ScriptPanelVC
                .filter { $0 }
                .skip(1)
                .subscribeNext {[weak self] _ in
                if let self = self {
                    self.resetManualStates()
                    self.refresh()
                }
            }
            // bind appContext to controls
            self.appContext.mainStates.$channelId.bindTo(self.inputChannelId)
            self.appContext.mainStates.$channelProfile.bindTo(self.selectChannelProfile) {[weak self] (select, value) in
                if let self = self {
                    let index = value.getCurrIndex()
                    if select.indexOfSelectedItem != index {
                        select.selectItem(at: index)
                    }
                    if value == .liveBroadcasting && self.selectClientRole.indexOfSelectedItem == 0 {
                        self.selectClientRole.selectItem(at: 1)
                        RACAssign(self.appContext.mainStates).clientRole = just(.broadcaster)
                    } else if value == .communication && self.selectClientRole.indexOfSelectedItem != 0 {
                        self.selectClientRole.selectItem(at: 0)
                        RACAssign(self.appContext.mainStates).clientRole = just(nil)
                    }
                }
            }
            self.appContext.mainStates.$clientRole.bindTo(self.selectClientRole) {[weak self] (select, value) in
                if let self = self {
                    var index = 0
                    if let value = value {
                        index = value.getCurrIndex() + 1
                    }
                    if self.selectChannelProfile.indexOfSelectedItem == 1 && index == 0 {
                        index = 1
                        RACAssign(self.appContext.mainStates).clientRole = just(.broadcaster)
                    } else if self.selectChannelProfile.indexOfSelectedItem == 0 && index != 0 {
                        index = 0
                        RACAssign(self.appContext.mainStates).clientRole = just(nil)
                    }
                    if select.indexOfSelectedItem != index {
                        select.selectItem(at: index)
                    }
                }
            }
            self.appContext.mainStates.$token.bindTo(self.inputToken)
            self.appContext.mainStates.$tokenType.bindTo(self.selectTokenType)
            self.appContext.mainStates.$localUID.bindTo(self.inputUserId) {(ctrl, value) in
                if value > 0 {
                    ctrl.flText = "\(value)"
                }
            }
            self.appContext.mainStates.$hasJoined.signal()
                .take(until: self.btnJoinChannel.rac_willDealloc)
                .subscribeNext {[weak self] val in
                    if let self = self {
                        self.inputChannelId.isEnabled = !val
                        self.inputAppId.isEnabled = !val
                        self.inputUserId.isEnabled = !val
                        self.inputToken.isEnabled = !val
                        self.selectChannelProfile.isEnabled = !val
                        self.selectClientRole.isEnabled = !val
                        self.selectTokenType.isEnabled = !val
                        self.btnJoinChannel.flTitle = val ? "Leave Channel" : "Join Channel"
                        if !val && self.inputUserId.flText.count > 0 {
                            self.inputUserId.flText = self.manualInputUserId == nil ? "" : "\(self.manualInputUserId!)"
                        }
                    }
                }
            self.appContext.mainStates.$captureSharing.signal()
                .take(until: self.btnStopCapture.rac_willDealloc)
                .skip(1)
                .subscribeNext {[weak self] val in
                    if let self = self {
                        self.refresh()
                    }
                }
            self.appContext.scriptsInvoker.$runningMode.signal()
                .take(until: self.btnJoinChannel.rac_willDealloc)
                .subscribeNext {[weak self] val in
                    if let self = self {
                        self.btnSettings.isEnabled = val == .notStart
                        self.btnBeforeParams.isEnabled = val == .notStart
                        self.btnAfterParams.isEnabled = val == .notStart
                        self.btnJoinChannel.isEnabled = val != .notStart && self.appContext.scriptsInvoker.invokeMode == .manual
                    }
                }
            self.appContext.$settingsUseMode.signal().skip(1).subscribeNext {[weak self] mode in
                if let self = self {
                    switch mode {
                    case .notUsed:
                        self.imgSettingsTips.image = nil
                    case .usedByScript:
                        self.imgSettingsTips.image = NSImage(named: "icon_tips")
                    case .usedByManual:
                        self.imgSettingsTips.image = NSImage(named: "icon_warning")
                    }
                    self.imgSettingsTips.removeFromSuperview()
                    self.imgSettingsTips.frame = CGRect(x: 82, y: 18, width: 16, height: 16)
                    self.btnSettings.addSubview(self.imgSettingsTips, positioned: .above, relativeTo: nil)
                }
            }
            // observer controls value back to appContext
            RACAssign(self.appContext.mainStates).appId = self.selectAppId.rac_eventTouch
                .map { [weak self] ctrl in
                    self?.resetManualStates()
                    self?.manualSelectAppId = true
                    return ctrl.selectedComplexEnumIndex
                }
            RACAssign(self.appContext.mainStates).appId = self.inputAppId.rac_valueDidChanged
                .map { [weak self] value -> AppAppIdType in
                    self?.resetManualStates()
                    self?.manualInputAppId = true
                    return .custom(appId: value)
                }
            RACAssign(self.appContext.mainStates).channelProfile = self.selectChannelProfile.rac_eventTouch
                .map {[weak self] ctrl in
                    self?.resetManualStates()
                    self?.manualSelectChannelProfile = true
                    return ctrl.selectedComplexEnumIndex
                }
            RACAssign(self.appContext.mainStates).clientRole = self.selectClientRole.rac_eventTouch.map { select -> AgoraClientRole? in
                let index = select.selectedComplexEnumIndex
                return AgoraClientRole.getCaseItem(by: index - 1)
            }
            RACAssign(self.appContext.mainStates).tokenType = self.selectTokenType.rac_eventTouch
                .map {[weak self] ctrl in
                    self?.resetManualStates()
                    self?.manualSelectTokenType = true
                    return ctrl.selectedComplexEnumIndex
                }
            RACAssign(self.appContext.mainStates).token = self.inputToken.rac_valueDidChanged
                .map {[weak self] val -> String in
                    if let self = self {
                        if self.selectTokenType.indexOfSelectedItem != 0 {
                            self.selectTokenType.selectItem(at: 0)
                            RACAssign(self.appContext.mainStates).tokenType = just(.static)
                        }
                    }
                    return val
                }
            RACAssign(self.appContext.mainStates).localUID = self.inputUserId.rac_valueDidChanged
                .map { Int($0) ?? -1 }.filter { $0 >= 0 }
                .map {[weak self] val -> UInt in
                    let result = UInt(val)
                    self?.manualInputUserId = val == 0 ? nil : result
                    if val == 0 {
                        self?.inputUserId.flText = ""
                    }
                    return result
                }
                
            RACAssign(self.appContext.mainStates).actionRespondsScriptAction = self.btnJoinChannel.rac_eventTouch
                                                                                .map {[weak self] _ in
                                                                                    if let self = self {
                                                                                        if !self.appContext.mainStates.hasJoined {
                                                                                            return ActionNameJoinChannel
                                                                                        }
                                                                                    }
                                                                                    return ActionNameLeaveChannel
                                                                                }
            self.btnSettings.rac_eventTouch.subscribeNext {[weak self] _ in
                if let self = self {
                    let panel = ConfigSDKSettingsVC()
                    self.presentAsModalWindow(panel)
                }
            }
            self.btnBeforeParams.rac_eventTouch.subscribeNext {[weak self] btn in
                FLShowCloseAlert("该功能即将开放使用")
                btn.flTitle = btn.flTitle.switchButtonTitle()
            }
            self.btnAfterParams.rac_eventTouch.subscribeNext {[weak self] btn in
                FLShowCloseAlert("该功能即将开放使用")
                btn.flTitle = btn.flTitle.switchButtonTitle()
            }
            self.btnStopCapture.rac_eventTouch.subscribeNext {[weak self] _ in
                if let self = self {
                    self.appContext.scriptsInvoker.callCustomAction(ActionNameStopCaptureSharing)
                }
            }
            // handle linkage of ChannelProfile、TokenType and AppId
            let combineAppIdInputs = combine(self.appContext.mainStates.$channelProfile.signal(),
                                             self.appContext.mainStates.$tokenType.signal(),
                                             self.appContext.mainStates.$appId.signal())
                                         .take(until: self.view.rac_willDealloc)
                                         .map { [weak self] args -> String in
                                            var result = ""
                                            if let self = self {
                                                result = self.appContext.mainStates.appId.rawValue
                                                let channelProfile = args.0 ?? self.appContext.mainStates.channelProfile
                                                let tokenType = args.1 ?? self.appContext.mainStates.tokenType
                                                let appIdType = args.2 ?? self.appContext.mainStates.appId
                                                if tokenType != .static && self.inputToken.flText.count > 0 {
                                                    self.inputToken.flText = ""
                                                }
                                                if case .custom(let appId) = appIdType,
                                                   (self.manualSelectAppId || self.manualInputAppId || appId.count > 0) {
                                                    // set custom appId to inputAppId
                                                    let index = self.selectAppId.itemTitles.count - 1
                                                    if !self.inputAppId.isEnabled
                                                        || appId != self.inputAppId.flText
                                                        || index != self.selectAppId.indexOfSelectedItem {
                                                        self.inputAppId.flText = appId
                                                        self.selectAppId.selectItem(at: index)
                                                        self.inputAppId.isEnabled = true
                                                        result = appId
                                                    }
                                                } else {
                                                    var index = self.selectAppId.indexOfSelectedItem
                                                    if !(self.manualSelectAppId || self.manualInputAppId) {
                                                        let newIndex = AppAppIdType.selectIndex(by: tokenType, and: channelProfile)
                                                        if index != newIndex {
                                                            index = newIndex
                                                        }
                                                    }
                                                    let appId = AppAppIdType.allCaseItems[index]
                                                    if appId.rawValue != self.inputAppId.flText {
                                                        self.inputAppId.flText = appId.rawValue
                                                        self.selectAppId.selectItem(at: index)
                                                        self.inputAppId.isEnabled = false
                                                        result = appId.rawValue
                                                    }
                                                }
                                            }
                                            return result
                                         }
            RACAssign(self.appContext.mainStates).appId = combineAppIdInputs.filter {[weak self] appId in
                if let self = self {
                    return appId != self.appContext.mainStates.appId.rawValue
                }
                return false
            }.map { AppAppIdType.init(stringLiteral: $0) }
        }
    }
    
    override func layoutUI(needsLayout: Bool, args: [String : Any?]? = nil) {
        self.dataBinding(needsLayout)
        self.layoutAppContextStates(needsLayout)
        self.layoutCustomStatesNActions(needsLayout)
    }
    
    // REMARK: layout appContext states
    private func layoutAppContextStates(_ needsLayout: Bool) {
        self.appContextSpan.bind(to: yoga, self.view) { it in
            it.marginHorizontal = 4
            it.marginTop = 4
        }
        // line-1
        let line1Span = NSView()
        line1Span.bind(to: yoga, self.appContextSpan) { it in
            it.direction = .row
            it.alignItems = .center
            it.height = 30
        }
        // ChannelId
        let lblChannelId = FLLabel()
        lblChannelId.bind(to: yoga, line1Span) { it in
            it.height = 20
            it.minWidth = 70
        }
        lblChannelId.flText = "ChannelId: "
        lblChannelId.textColor = .black
        lblChannelId.font = NSFont.systemFont(ofSize: 12)
        self.inputChannelId.bind(to: yoga, line1Span) { it in
            it.height = 24
            it.width = 134
            it.marginLeft = 2
        }
        // ChannelProfile
        let lblChannelProfile = FLLabel()
        lblChannelProfile.bind(to: yoga, line1Span) { it in
            it.height = 20
            it.marginLeft = 4
        }
        lblChannelProfile.flText = "ChannelProfile: "
        lblChannelProfile.textColor = .black
        lblChannelProfile.font = NSFont.systemFont(ofSize: 12)
        self.selectChannelProfile.bind(to: yoga, line1Span) { it in
            it.width = 140
            it.height = 30
        }
        // ClientRole
        let lblClientRole = FLLabel()
        lblClientRole.bind(to: yoga, line1Span) { it in
            it.height = 20
            it.minWidth = 70
            it.marginLeft = 4
        }
        lblClientRole.flText = "ClientRole: "
        lblClientRole.textColor = .black
        lblClientRole.font = NSFont.systemFont(ofSize: 12)
        self.selectClientRole.bind(to: yoga, line1Span) { it in
            it.width = 140
            it.height = 30
        }
        // AppId
        let lblAppId = FLLabel()
        lblAppId.bind(to: yoga, line1Span) { it in
            it.height = 20
            it.marginLeft = 4
        }
        lblAppId.flText = "AppId: "
        lblAppId.textColor = .black
        lblAppId.font = NSFont.systemFont(ofSize: 12)
        self.selectAppId.bind(to: yoga, line1Span) { it in
            it.width = 186
            it.height = 30
        }
        self.inputAppId.bind(to: yoga, line1Span) { it in
            it.width = 238
            it.height = 24
        }
        // UserId
        let lblUserId = FLLabel()
        lblUserId.bind(to: yoga, line1Span) { it in
            it.height = 20
            it.marginLeft = 4
        }
        lblUserId.flText = "UserId: "
        lblUserId.textColor = .black
        lblUserId.font = NSFont.systemFont(ofSize: 12)
        self.inputUserId.bind(to: yoga, line1Span) { it in
            it.width = 134
            it.height = 24
        }
        // line-2
        let line2Span = NSView()
        line2Span.bind(to: yoga, self.appContextSpan) { it in
            it.direction = .row
            it.alignItems = .center
            it.height = 30
        }
        // token
        let lblTokenType = FLLabel()
        lblTokenType.bind(to: yoga, line2Span) { it in
            it.height = 20
            it.minWidth = lblMinWidth.toFLValue()
        }
        lblTokenType.flText = "TokenType: "
        lblTokenType.textColor = .black
        lblTokenType.font = NSFont.systemFont(ofSize: 12)
        self.selectTokenType.bind(to: yoga, line2Span) { it in
            it.width = 140
            it.height = 30
        }
        let lblToken = FLLabel()
        lblToken.bind(to: yoga, line2Span) { it in
            it.height = 20
            it.minWidth = 40
        }
        lblToken.flText = "Token: "
        lblToken.textColor = .black
        lblToken.font = NSFont.systemFont(ofSize: 12)
        self.inputToken.bind(to: yoga, line2Span) { it in
            it.marginLeft = 4
            it.width = 180
            it.height = 24
        }
        // buttons
        let rightOfLine2Span = NSView()
        rightOfLine2Span.bind(to: yoga, line2Span) { it in
            it.position = .absolute
            it.direction = .rowReverse
            it.right = 0
        }
        
        self.btnStopCapture.bind(to: yoga, rightOfLine2Span) { it in
            it.width = 90
            it.height = 26
            it.marginLeft = 4
            it.display = self.appContext.mainStates.captureSharing ? .flex : .none
        }
        self.btnStopCapture.flTitle = "Stop Capture"
        self.btnStopCapture.flTitleColor = .white
        self.btnStopCapture.flBackgroundColor = .brown
        self.btnStopCapture.flCornerRadius = 4

        self.btnJoinChannel.bind(to: yoga, rightOfLine2Span) { it in
            it.width = 90
            it.height = 26
            it.marginLeft = 24
        }
        self.btnJoinChannel.flTitle = self.appContext.mainStates.hasJoined ? "Leave Channel" : "Join Channel"
        self.btnJoinChannel.flTitleColor = .white
        self.btnJoinChannel.flBackgroundColor = .darkGray
        self.btnJoinChannel.flCornerRadius = 4
        
        self.btnAfterParams.bind(to: yoga, rightOfLine2Span) { it in
            it.width = 90
            it.height = 26
            it.marginLeft = 4
        }
        self.btnAfterParams.flTitle = "After Params"
        self.btnAfterParams.flTitleColor = .white
        self.btnAfterParams.flBackgroundColor = .gray
        self.btnAfterParams.flCornerRadius = 4

        self.btnBeforeParams.bind(to: yoga, rightOfLine2Span) { it in
            it.width = 90
            it.height = 26
        }
        self.btnBeforeParams.flTitle = "Before Params"
        self.btnBeforeParams.flTitleColor = .white
        self.btnBeforeParams.flBackgroundColor = .gray
        self.btnBeforeParams.flCornerRadius = 4

        self.btnSettings.bind(to: yoga, rightOfLine2Span) { it in
            it.width = 90
            it.height = 26
            it.marginRight = 12
        }
        self.btnSettings.flTitle = "SDK Settings"
        self.btnSettings.flTitleColor = .white
        self.btnSettings.flBackgroundColor = .gray
        self.btnSettings.flCornerRadius = 4
    }
    
    // REMARK: layout custom states & actions
    private func layoutCustomStatesNActions(_ needsLayout: Bool) {
        let lineWidth: CGFloat = 1440 - 4 * 2
        let result = self.appContext.getCustomMappingControls(setStatesForSettings: true, filterSettings: false)
        var lineIndex = 0
        var currWidth: CGFloat = 0
        var lineSpan: NSView! = nil
        var firstControl = true
        var index = 0
        while index < result.count {
            let (keyPath, aliasName, _, mappingControl) = result[index]
            if lineSpan == nil {
                lineSpan = NSView()
                lineSpan.bind(to: yoga, self.customSpan) { it in
                    it.direction = .row
                    it.height = 30
                    it.alignItems = .center
                }
                lineIndex += 1
            }
            let marginLeft = firstControl ? 0 : (mappingControl == nil ? btnMarginLeft : lblMarginLeft)
            var width: CGFloat = 0
            if let control = mappingControl, control.type != .none {
                if control.type == .checkbox {
                    width = lblMinWidth + marginLeft
                } else {
                    width = lblMinWidth + controlWidths[control.type]! + controlMarginLefts[control.type]! + marginLeft
                }
            } else {
                width = btnMinWidth + marginLeft
            }
            if currWidth + width > lineWidth {
                currWidth = 0
                lineSpan = nil
                firstControl = true
            } else {
                if let control = mappingControl, control.type != .none {
                    // checkbox
                    if control.type == .checkbox {
                        let chk = FLCheckbox()
                        chk.bind(to: yoga, lineSpan) { it in
                            it.minWidth = lblMinWidth.toFLValue()
                            it.height = btnHeight.toFLValue()
                            it.marginLeft = marginLeft.toFLValue()
                        }
                        chk.font = NSFont.systemFont(ofSize: 12)
                        chk.flTitle = aliasName
                        // data binding
                        self.appContext.bindTo(keyPath: keyPath, chk) {(ctrl, value) in
                            if let state = value as? Bool {
                                let newValue: NSControl.StateValue = state ? .on : .off
                                if ctrl.state != newValue {
                                    ctrl.state = newValue
                                }
                            }
                        }
                        self.appContext.subscribe(keyPath: keyPath, chk, control)
                    } else {
                        // label
                        let lbl = FLLabel()
                        lbl.bind(to: yoga, lineSpan) { it in
                            it.marginLeft = marginLeft.toFLValue()
                            it.height = lblHeight.toFLValue()
                            it.minWidth = lblMinWidth.toFLValue()
                        }
                        lbl.textColor = .black
                        lbl.font = NSFont.systemFont(ofSize: 12)
                        lbl.flText = aliasName
                        // select
                        if control.type == .dropbox {
                            let select = NSPopUpButton()
                                select.bind(to: yoga, lineSpan) { it in
                                it.marginLeft = selectMarginLeft.toFLValue()
                                it.width = selectWidth.toFLValue()
                                it.height = selectHeight.toFLValue()
                            }
                            // data binding
                            var items: [String]! = nil
                            var selectIndex = 0
                            if let enumClass = control.enumClass {
                                items = getCaseItemDescriptions(by: enumClass, defaultItem: control.defaultSelectItem, self.appContext.engine)
                            } else if let strItems = control.stringItems {
                                items = Array(strItems)
                                if let defaultItem = control.defaultSelectItem {
                                    items.insert(defaultItem, at: 0)
                                }
                            }
                            select.addItems(withTitles: items)
                            // bindTo control
                            self.appContext.bindTo(keyPath: keyPath, select) {[weak self] (ctrl, value) in
                                if let self = self {
                                    if let enumClass = control.enumClass {
                                        selectIndex = selectIndexOfEnum(enumName:enumClass, value, self.appContext.engine)
                                        if control.defaultSelectItem != nil {
                                            selectIndex += 1
                                        }
                                        ctrl.selectItem(at: selectIndex)
                                    } else if let initValue = value as? String,
                                              let index = ctrl.itemTitles.firstIndex(where: { $0 == initValue }) {
                                        ctrl.selectItem(at: index)
                                    }
                                }
                            }
                            self.appContext.subscribe(keyPath: keyPath, select, control)
                        } else if control.type == .inputField || control.type == .inputFile || control.type == .inputBinary {
                            // input
                            let input = FLInputField()
                            input.bind(to: yoga, lineSpan) { it in
                                it.marginLeft = inputMarginLeft.toFLValue()
                                it.width = inputWidth.toFLValue()
                                it.height = inputHeight.toFLValue()
                            }
                            // data binding
                            self.appContext.bindTo(keyPath: keyPath, input) {(ctrl, value) in
                                let (_, valueStr) = getAnyValueStr(from: value)
                                if input.flText != valueStr {
                                    input.flText = valueStr
                                }
                            }
                            self.appContext.subscribe(keyPath: keyPath, input, control)
                        }
                    }
                } else {
                    let btn = NSButton()
                    btn.bind(to: yoga, lineSpan) { it in
                        it.marginLeft = marginLeft.toFLValue()
                        it.minWidth = btnMinWidth.toFLValue()
                        it.height = btnHeight.toFLValue()
                    }
                    btn.flTitle = aliasName
                    btn.flTitleColor = .white
                    btn.flBackgroundColor = .darkGray
                    btn.flCornerRadius = 4
                    // data binding
                    if let control = mappingControl, let actionArguments = control.associatedActionArguments {
                        for (aliasName, predicate) in actionArguments {
                            self.appContext.subscribe(predicate, btn, aliasName: aliasName)
                        }
                    }
                    RACAssign(self.appContext.mainStates).actionRespondsScriptAction = btn.rac_eventTouch.map { _ in keyPath }
                }
                currWidth += width
                index += 1
                if firstControl {
                    firstControl = false
                }
            }
        }
        
        self.customSpan.bind(to: yoga, self.view) { it in
            it.marginHorizontal = 4
            it.height = (30 * lineIndex).toFLValue()
        }
    }
}
